#!/usr/bin/python2
# coding=utf-8

"""
    Ball Fall - a 2d arcade game written in Python
    Copyright (C) 2008 Bartosz Gąsiorzewski <g.bartek@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import sys, pygame, time, random, ConfigParser
from timer import Timer
from rain_utils import *

def killBall():
    global lives
    lives -= 1
    if lives <= 0:
        print "GAME OVER"
        print "%s You gained %d points (game speed %d)." \
                % (time.strftime("%H:%M:%S"), score, aps)
        sys.exit()
    elif lives == 1:
        print time.strftime("%H:%M:%S"), "Oops! You have got 1 life left."
    else:
        print time.strftime("%H:%M:%S"),
        print "Oops! You have got %d lives left." % lives
    # move ball onto the latest bar
    ballrect.bottom = barrects[(bar_counter - 1) % max_bars].top
    ballrect.centerx = barrects[(bar_counter - 1) % max_bars].centerx

def addLife():
    global lives, heartrect
    heartrect.bottom = 0
    if lives < lives_limit:
        lives += 1
    print time.strftime("%H:%M:%S"),
    print "Hooray! You have got %d lives now." % lives

def speedUp():
    global aps, tpa, t
    aps += si_amount
    tpa = 1.0 / aps
    t.interval = tpa

config = ConfigParser.RawConfigParser()
config.read(['config.ini'])

# game settings (affecting difficulty, performance and other stuff)
# for explanations, see comments in the configuration file
aps = config.getint('general', 'start_aps')
fps_limit = config.getint('general', 'fps_limit')
si_interval = config.getfloat('general', 'si_interval')
si_amount = config.getint('general', 'si_amount')
bar_min_chance = config.getint('general', 'bar_min_chance')
bar_max_chance = config.getint('general', 'bar_max_chance')
max_bars = config.getint('general', 'max_bars')
thorn_min_chance = config.getint('general', 'thorn_min_chance')
thorn_max_chance = config.getint('general', 'thorn_max_chance')
max_thorns = config.getint('general', 'max_thorns')
fps_interval = config.getint('general', 'fps_interval')
ball_hori_speed = config.getint('general', 'ball_hori_speed')
ball_vert_speed = config.getint('general', 'ball_vert_speed')
everything_speed = config.getint('general', 'everything_speed')
lives = config.getint('general', 'start_lives')
lives_limit = config.getint('general', 'lives_limit')
heart_create_interval = config.getint('general', 'heart_create_interval')
pause_interval = config.getint('general', 'pause_interval')
output_fps = config.getint('general', 'output_fps')

# graphic settings
background_color = (0, 0, 0)

fpsxy = (732, 10)
fps_family = 'freesansbold.ttf'
fps_size = 30
fps_color = (64, 0, 96)

scorexy = (120, 10)
score_family = 'freesansbold.ttf'
score_size = 30
score_color = (64, 0, 96)

# free space between two lives
life_margin = 4
livesxy = (447, 10)

dimensions = width, height = 800, 600
# height of the restricted score area (ball is killed when enters this area)
spikes_height = 50

# some variables
frame_pts = 0
speed = [0, 0]
direction = 0 # direction in which ball is moving, {-1, 0, 1}
tpa = 1.0 / aps # time per action = aps^-1
tick = 1.0 / fps_limit # lower = more fps, higher = better performance

# initialize the environment
pygame.init()
icon = pygame.image.load('ball.png')
pygame.display.set_icon(icon)
pygame.display.set_caption('Ball Fall')
window = pygame.display.set_mode(dimensions)

# game objects
ball = pygame.image.load('ball.png') # don't convert, the ball has alpha
ballrect = ball.get_rect()
ballrect.center = (width / 2, height / 2)

heart = pygame.image.load('life.png') # has alpha
heartrect = heart.get_rect()
heart_every = every(1.0 / heart_create_interval)

barrects = []
bar = pygame.image.load('bar.png').convert(window)
for i in range(max_bars):
    barrect = bar.get_rect()
    barrects.append(barrect)
# create the initial bar
barrects[0].top = height
barrects[0].centerx = width / 2
bar_counter = 1
bar_rand = myRand(bar_min_chance, bar_max_chance)

thornrects = []
# FIXME: thorn.png sucks, thorn.jpg sucks too and is stolen from somewhere
thorn = pygame.image.load('thorn.jpg')
for i in range(max_thorns):
    thornrect = thorn.get_rect()
    thornrects.append(thornrect)
thorn_counter = 0
thorn_rand = myRand(thorn_min_chance, thorn_max_chance)

# scorebar
scorebar = pygame.image.load("scorebar.png").convert(window)
scorebarrect = scorebar.get_rect()

score = 0
score_font = pygame.font.SysFont(score_family, score_size)
score_display = score_font.render(str(score), True, score_color)

liferects = []
life = pygame.image.load('life.png')
for i in range(lives_limit):
    liferects.append(life.get_rect())
    liferects[i].top = livesxy[1]
    liferects[i].left = livesxy[0] + i * (liferects[i].width + life_margin)

fps = 0
fps_every = every(1.0 / fps_interval)
fps_font = pygame.font.SysFont(fps_family, fps_size)
fps_display = fps_font.render(str(fps), True, fps_color)

# speed increase, increase speed every aps * si_interval actions
# equivalent to increasing every si_interval seconds,
# but can be paused
si_every = every(1.0 / aps / si_interval)

# timer limiting output updating (drawing) to output_fps
output_timer = Timer(1.0 / output_fps)
output_timer.start()

# fps limit timer, sleep after each loop iteration
t = Timer(tpa)
t.start()

# prepare variables for counting fps
total_frames = last_frames = 0
last = time.time()

pause = False
last_pause = 0.0

while True:
    elapsed = t.getElapsed()
    if elapsed > 0:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            if event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT:
                direction = -1
            if event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
                direction = 1
            if event.type == pygame.KEYUP and event.key == pygame.K_LEFT:
                if direction == -1: direction = 0
            if event.type == pygame.KEYUP and event.key == pygame.K_RIGHT:
                if direction == 1: direction = 0
            if event.type == pygame.KEYDOWN and event.key == pygame.K_p:
                # toggle pause
                if pause == True:
                    pause = False
                else:
                    # don't let players abuse pausing
                    if (time.time() - last_pause) > pause_interval:
                        pause = True
                        last_pause = time.time()
        
        # draw FPS meter
        for i in range(elapsed):
            if fps_every.next():
                now = time.time()
                if now - last <= 0.0:
                    # don't divide by 0, update FPS next time
                    break
                since_frames = (total_frames - last_frames)
                fps = min(999, int(since_frames / (now - last)))
                last_frames = total_frames
                last = now
                fps_display = fps_font.render(str(fps), True, fps_color)
        
        if not pause:
            speed[0] = direction * ball_hori_speed
            frame_pts = speed[1] = ball_vert_speed
            
            # move things around
            ballrect = ballrect.move(elapsed * speed[0], elapsed * speed[1])
            ballrect = ballrect.move(0, elapsed * everything_speed)
            heartrect = heartrect.move(0, elapsed * everything_speed)
            for i in range(len(barrects)):
                barrects[i] = barrects[i].move(0, elapsed * everything_speed)
            for i in range(len(thornrects)):
                thornrects[i] = thornrects[i].move(0,
                        elapsed * everything_speed)
            
            for barrect in barrects:
                if ballrect.right - elapsed * speed[0] > barrect.left \
                and ballrect.left - elapsed * speed[0] < barrect.right \
                and ballrect.bottom - elapsed * speed[1] <= barrect.top \
                and ballrect.right > barrect.left \
                and ballrect.left < barrect.right \
                and ballrect.bottom > barrect.top:
                    # ball between, both before and after movement
                    # before the movement ball was above,
                    # after the movement it's below
                    
                    # remove the collision
                    ballrect.bottom = barrect.top
                    # no vertical movement = no points
                    frame_pts = 0
            
            if ballrect.collidelist(thornrects) != -1:
                killBall()
            
            # life pick-up
            if ballrect.colliderect(heartrect):
                addLife()
            
            # ball and window borders
            if ballrect.left < 0:
                ballrect.left = 0
            if ballrect.right > width:
                ballrect.right = width
            if ballrect.bottom > height:
                killBall()
            if ballrect.top < spikes_height:
                killBall()
            
            # update score
            if speed[1] > 0:
                # update only when necessary
                score += frame_pts * elapsed
                score_display = score_font.render(str(score), True, score_color)
            
            # create next bars and thorns and hearts
            for i in range(elapsed):
                if bar_rand.next():
                    if thorn_rand.next():
                        # don't create a bar, create a thorn
                        thornrects[thorn_counter].top = height
                        thornrects[thorn_counter].left = random.randint(0,
                                width - thornrects[thorn_counter].width)
                        thorn_counter = (thorn_counter + 1) % max_thorns
                        continue
                    # create new bar
                    barrects[bar_counter].top = height
                    barrects[bar_counter].left = random.randint(0,
                                    width - barrects[bar_counter].width)
                    # create new heart
                    if heart_every.next():
                        heartrect.bottom = height
                        heartrect.left = random.randint(
                                barrects[bar_counter].left,
                                barrects[bar_counter].right - heartrect.width)
                    bar_counter = (bar_counter + 1) % max_bars
            
            # make game harder by speeding it up
            for i in range(elapsed):
                if si_every.next():
                    speedUp()
                    # aps * seconds = actions
                    # so we need to increase speed
                    # every aps * si_interval actions
                    si_every = every(1.0 / si_interval / aps)
        # endif not pause
        
        if output_timer.getElapsed():
            # updating output is the slowest thing out there,
            # so don't do it more often than it's required
            window.fill(background_color)
            # draw ball, heart, bars and thorns
            if heartrect.bottom > spikes_height:
                window.blit(heart, heartrect)
            for i in range(len(barrects)):
                if barrects[i].bottom > spikes_height:
                    window.blit(bar, barrects[i])
            # draw ball after (=over, in front of) bars
            window.blit(ball, ballrect)
            for i in range(len(thornrects)):
                if thornrects[i].bottom > spikes_height:
                    window.blit(thorn, thornrects[i])
            # draw background and scorebar
            window.blit(scorebar, scorebarrect)
            window.blit(score_display, scorexy)
            window.blit(fps_display, fpsxy)
            for i in range(lives):
                window.blit(life, liferects[i])
            pygame.display.flip()
    
    # limit fps
    time.sleep(tick)
    total_frames += 1
